package org.NooLab.somclients;

 
import java.util.Map;

import org.NooLab.astor.app.up.AstorSomStartup;
import org.NooLab.field.FieldIntf;
import org.NooLab.itexx.storage.TexxDataBaseSettingsIntf;
import org.NooLab.somclients.app.up.IniProperties;
import org.NooLab.somclients.app.up.SomClientsStartup;
import org.NooLab.somclients.components.SomClientsFactory;
import org.NooLab.somclients.components.SomClientsPropertiesPublicIntf;
import org.NooLab.somclients.components.params.SomClientParamPkgIntf;
import org.NooLab.somclients.domains.ScSomfixParamPkgIntf;
import org.NooLab.somclients.domains.SomClientIntf;
import org.NooLab.somclients.intf.SomClientsPublicIntf;
import org.NooLab.somfluid.properties.SomDynamicsIntf;
import org.NooLab.structures.AppInstanceAbstract;
import org.NooLab.structures.ArgumentsCliReaderAbstract;
import org.NooLab.structures.ArgumentsIntf;
import org.NooLab.structures.ArgumentsPublicIntf;
import org.NooLab.structures.InstanceProcessControlIntf;
import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.files.PathFinder;
import org.NooLab.utilities.logging.LogControl;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.resources.ResourceLoader;
import org.NooLab.utilities.strings.StringsUtil;

 

/**
 * 
 * the SomClientsApp starts only once within iTexx, via object  "CoSomClients"
 * 
 *       as a CLI argument it takes the name of the database
 * 
 * Once started, it listens and waits waits for packages of the format SomClientParamPkgIntf or similar
 * through tcp
 * 
 * 
 *
 */
public class SomClientsApp {


	
	
	public static void main(String[] args) {
	
		try {
			
			new SomClientsApp(args);
				
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

	public SomClientsApp(String[] args) throws Exception {
		
		SomClientsStartup.setApplicationID("somclients", this.getClass());

		(new App( 0, (new Arguments()).parse(args), this.getClass())).go();
	}

}


//----------------------------------------------------------------------------


@SuppressWarnings("rawtypes")
class App{
	
	SomClientsAppInstance scaInstance;
	
	ArgumentsPublicIntf arguments;
	
	PrintLog out ;
	StringsUtil strgutil = new StringsUtil ();
	DFutils fileutil = new DFutils();
	boolean somInstanceStopped;

	// ========================================================================
	public App( int as_service, ArgumentsPublicIntf args, Class pubClass) throws Exception {	
	
		String fname = "" ; 
		
		arguments = args;
		
		if ((arguments.getServicePorts()[0]<1090) || (arguments.getPorts()[0]<1090)){
			throw(new Exception("Only port numbers larger than 1100 are allowed."));
		}
		
		// use LogControl to control output of log messages
		LogControl.globeScope = 2; // 1=local definitions  default = 2
		LogControl.Level = 2 ;     // the larger the more detailed logging... (could be really verbose!)

		out = new PrintLog(2, true) ;
		
		try {
			String lastCfgFile ="" ;
			
			
			SomClientsStartup.setLastProjectName("itx");
			
			IniProperties.loadIniFile() ;
			
			lastCfgFile = SomClientsStartup.getLastDataSet();
			if ((lastCfgFile==null) || (lastCfgFile.trim().length()<=1)){
				if (SomClientsStartup.getApplicationID().length()==0 ){
					SomClientsStartup.setApplicationID( this.getClass().getName().toLowerCase(),pubClass ) ;
				}
				
			}
			IniProperties.saveIniProperties() ;
			
			if (DFutils.fileExists(arguments.getConfigFile())==false){
				if ((arguments.getConfigFile()!=null) && (arguments.getConfigFile().length()>0)){

					
					fname = DFutils.createPath( arguments.getHomeDir(), "itx/config/") ;
					fname = DFutils.createPath( fname, arguments.getConfigFile()) ;
					if (DFutils.fileExists(fname)){
						arguments.setConfigFile (fname);
					}else{
						if (fileutil.filenameIsUsable(fname)){
							arguments.setConfigFile (fname);
						}
					}
				}
				
				
				if (DFutils.fileExists(arguments.getConfigFile())==false){

					/*
					createDefaultConfig();

					
					String msgstr = "you now have to edit the configuration file : \n"+ arguments.configFile;
					SomClientsStartup.showMsg(msgstr);

					// DocoServStartup.introduceDataSet( arguments.configFile ) ;
			
					IniProperties.saveIniProperties() ;
					// copy the filename to the system clipboard
					System.exit(0);
					
					*/
				}else{
					
				}
				
			}
			
			IniProperties.saveIniProperties() ;
			
			_showCurrentInputSettings();
			
			
			fname=fname+" " ;
			
		} catch (Exception e) {
			e.printStackTrace();
		}

		
		
	}
	

	private boolean checkForAppletMode() {

		boolean rB=false;
		 
		return rB ;
	}

	private void createDefaultConfig() throws Exception {
		 
		String filename = "", defaultCfg , filstr,resourcePath;
		boolean rB;

		resourcePath = "org/NooLab/somclients/resources/somclients-config-xml" ;
		ResourceLoader rsrcLoader = new ResourceLoader();   
		
		rB = rsrcLoader.loadTextResource( this.getClass(), resourcePath  ) ;
		if (rB){  
			filstr = rsrcLoader.getTextResource();
			
			filename = arguments.getConfigFile() ;
			
			if ( DFutils.fileExists(filename)){
				fileutil.manageBakFile(filename, 10);
				fileutil.writeFileSimple( filename , filstr);
			}
			
		}else{
			throw(new Exception("unable to load resources (somclients-config-xml) or \n"+
					            "to create a new configuration file : "+filename)) ;
		}


		
	}


	
	private void _showCurrentInputSettings(){
		
		out.print(3,"current home dir : "+ SomClientsStartup.getProjectBasePath());
	}

	
	public void go() {


		somInstanceStopped = false ;
		
		startEngines();
		
		PrintLog.Delay(2000) ;
		// we stop from the outside by writing a file "stop" to the config directory
		while ( somInstanceStopped == false){
			PrintLog.Delay(100) ;
		}
		scaInstance.close(0) ;

	}

	
	/**
	 * 
	 * @param somtype 0=default: simple, single run; 1=som optimizer
	 * @param lastProjectName2 
	 */
	private void startEngines(){
		
		if (scaInstance!=null){
			return;
		}
		
		// setting worktypes
		
		scaInstance = new SomClientsAppInstance( this );
		
		if (arguments!=null){
			 
		
			scaInstance.homedir  = arguments.getHomeDir() ;
			scaInstance.logLevel = arguments.getLogLevel() ;
			
			scaInstance.glueMode = -1; // _GLUE_MODULE_ENV_NONE ;
			
			scaInstance.resumeMode = true;
			
			scaInstance.servicePorts = arguments.getServicePorts();
			scaInstance.systemPorts  = arguments.getPorts() ;
			
			scaInstance.appSecret    = (String)arguments.getMappedTopic("waitFor"); 
			scaInstance.processName  = (String)arguments.getMappedTopic("name");
			
		}
		
		 
							int oport = arguments.getOutPort();
							int iport = arguments.getInPort();

		scaInstance.prepareTcpBox(oport,iport);  // must be before the wait object !

		if (scaInstance.appSecret.length()>0){
			// waitfor-secret for testing is "adkhf-23487asdf-3487sdfr-324"
			out.print(2, "application will wait a few seconds for final clearance...");
			scaInstance.waitfor();
		}
		
		scaInstance.startInstance() ;
		
	}

	
}

/*
-homedir:"D:/data/iTexx/app/SomClients" 
-serviceports: in7202, out7201
-systemports: in7212, out7211

 */

@SuppressWarnings("rawtypes")
class Arguments extends ArgumentsCliReaderAbstract implements ArgumentsIntf {
	
	public final String _APP_COMPLETION_EXIT    = InstanceProcessControlIntf._APP_COMPLETION_EXIT ;
	public final String _APP_COMPLETION_SERVICE = InstanceProcessControlIntf._APP_COMPLETION_SERVICE ;
	
	public final String  _APP_CONTEXT_ITEXX = InstanceProcessControlIntf._APP_CONTEXT_ITEXX ;
	public final String  _APP_CONTEXT_ALONE = InstanceProcessControlIntf._APP_CONTEXT_ALONE ;
	

	String sourceFileDedicated="",sourceFilesByFolder="",sourceFileByListingFile="";
	
	protected ArgumentsPublicIntf arguments ;
	public Map argumentsMap;

		
	
	@Override
	protected ArgumentsPublicIntf parse(String[] _args) {
	 
		String avStr, str;
		
		/*

		-homedir:"D:/data/iTexx/app/SomClients" 
		-systemports: in7212, out7211
		-comm: in-7209,out-7210
		-waitfor: adkhf-23487asdf-3487sdfr-324
		-name:somclient

		 */		
		// jarDir = fileutil.getUserDir();
		str = fileutil.getUserHomeDir() ;
		str = IniProperties.fluidSomProjectBasePath; // before, we passed readbinpref() !
		homeDir = DFutils.createPath( str, "app/Astor/") ;
		
		
		defineArguments( new String[]{ "prj","homedir","name",
				                       "onCompletion",
				                       "context","waitfor",
				                       "loglevel"});
		
		try {
		
			arguments = prepareArguments( homeDir, _args ) ;

		} catch (Exception e) {
			showArguments( getDescription() );
			System.exit(0);			
		}  
		
		argumentsMap = getArgumentsMap();
		
		
		
		if (handlingTreatment( new String[]{"prj","proj","project"})){
		
			projectName = (String)getMappedTopic( new String[]{"prj","proj","project"} );
			
			
			AstorSomStartup.setLastProjectName(projectName);
			IniProperties.lastProjectName = projectName;
			
			str = IniProperties.getFluidSomProjectBasePath() ;
			if (IniProperties.getsBinPath().length()==0){
				
				str = (new PathFinder()).getAppBinPath(this.getClass(), false);
				IniProperties.setBinPath(str) ;
			}
			
			IniProperties.saveIniProperties();
			str = IniProperties.getFluidSomProjectBasePath() ;
		}
		
		setReference( (Object)this);
		
		return this;
	}

	
	
	@Override
	public String get_APP_COMPLETION_EXIT() {
		return _APP_COMPLETION_EXIT;
	}

	@Override
	public String get_APP_COMPLETION_SERVICE() {
		return _APP_COMPLETION_SERVICE;
	}

	@Override
	public String get_APP_CONTEXT_ITEXX() {
		return _APP_CONTEXT_ITEXX;
	}

	@Override
	public String get_APP_CONTEXT_ALONE() {
		return _APP_CONTEXT_ALONE;
	}

	public ArgumentsPublicIntf getArguments() {
		return arguments;
	}

	@Override
	public String[] getDescription() {
		String[] dParts = new String[3];
		
		dParts[0] = "Values (examples) appear subsequent to the : \n"+
		        "-prj:\"test2\"  \n"+
				"-homedir:\"D:/data/iTexx/app/Texx\" \n"+
				" \n" ;

		dParts[1] = 	"Default values are activated by parameter \"-default\" or \"-d\" and are set to: \n"+
			        "-prj: docoserv  \n"+
					"-homedir: <path of jar>/DocumentServer \n"+
					"\n";
		
		
		dParts[2] = "Additional Information: \n"+
		      "- The database file will be written to the folder \"<homedir>/<project name>/storage\".  \n"+
		      "- If no configuration file has been provided, a default file will be created into \n" +
		      "  the folder \"<homedir>/<project name>/config\". \n"+
		      "  Note that you then have to edit the configuration file, namely the section defining the \n"+
		      "  the folders to be observed.\n"+
		      "- You may stop the application by creating a file \"stop\" within the folder containing \n"+
		      "  the configuration file.  \n"+
		      "- The parameters for activating the notification are port numbers, (1) in, (2) out, where \n"+
		      "  the in-port allows for some udimaentary control, such like stopping the application, or \n"+
		      "  re-reading the configuration. \n"+
		      "- For starting the interactive applet version (e.g. for testing) use the \n"+
		      "  parameter \"-a\" or \"-applet\" \n\n"+" ";
		
		// System.err.println("You have to use start parameters: \n"); PrintLog.Delay(50) ;
		
		return dParts;
	}

	@Override
	public int[] getPorts() {
		int[] ports = new int[]{inPort,outPort};
		return ports;
	}
	

	@Override
	public int[] getServicePorts() {
		
		return servicePorts;
	}

	@Override
	public String getAppSecret() {
		return appsecret;
	}
	
	
}



class SomClientsAppInstance  extends AppInstanceAbstract{

	SomClientsPublicIntf somClients;
	App app;
	
	public int[] systemPorts;
	public int[] servicePorts;
	public Object logLevel;
	public boolean resumeMode;
	public Object glueMode;
	public String homedir;
	
	Thread soclThrd;
	
	// --------------------------------------------------------------
	public SomClientsAppInstance(App app) {
		super( "AstorSomApp", "soclInstance");
		
		processName = "SomClient";
		
		this.app = app;
		
		instance = this;
		out = app.out ;
	}
	// --------------------------------------------------------------
	
	
	public void startInstance() {
	 
		
		appSecret = app.arguments.getAppSecret() ;
		
		instanceThrd.start();
	}

	@Override
	protected void prepareInstance() {
		prepareSomClients();
	}


	@Override
	protected void resumeInstance() throws Exception {
		// TODO Auto-generated method stub
		
	}

	
	private void prepareSomClients() {
		
		
		// first setting the basic properties...
		
		SomClientsPropertiesPublicIntf scProperties ;
		
		// scProperties = somClients.getScProperties();  somClients.getScProperties is null here, still
		scProperties = SomClientsFactory.getProperties();
		
		
		scProperties.setHomeDir( homedir );
		scProperties.setSystemRootDir( DFutils.getParentDir(homedir, 2) );
		
		scProperties.setSystemPorts(systemPorts);
		scProperties.setServicePorts(servicePorts);
		

		// ... then creating and starting the instance
		somClients = SomClientsFactory.getInstance(scProperties) ;
		// now it is already started...
		
		
		/*
		 * nothing more to do here... any parameterization will be done by
		 * Astor-SOM (in SomAstor) as soon as the database "astornodes" has 
		 * been initialized
		 * 
		 */
		 
		
		PrintLog.Print(2, "\n...now testing (1)...");
		
		// starting the base engine
		
		PrintLog.Delay(5000);
		
		
		// testing a producer-package... this one would be called 
		// in astor som as a dependency
		SomClientParamPkgIntf pp ;
		pp = testPackage();
		
		// performs: somClients.waitingPackages.add(pp)
		String guid ;
		// this will add the package to a queue, which will be routed to "SomProducer"
		// via "SomClientProducer" and via "SomClients"...
		// there, an "astorInstance" will be created like in AstorSomApp, which then
		// will be put to SomFluid
		// guid = somClients.operateParameters(pp) ;
		 
		 
		// guid = somClients.operateParameters(pp) ;
	}
	 
	// testing routine for controlling a L2 SOM
	private SomClientParamPkgIntf testPackage(){
		
		int ct;
		ScSomfixParamPkgIntf paramsPkg ;
		// which is implemented by "SomClientParamPkg"...
		
		// we create a SOM...   e.g. also : SomClientIntf._SOMCLIENT_TYPE_DISPLAY !!
		ct = SomClientIntf._SOMCLIENT_TYPE_SOMDED;
		
		paramsPkg = (ScSomfixParamPkgIntf)somClients.getParameterPackage( ct ) ;
		// its parent is "SomClientParamPkgIntf"
		
		paramsPkg.setInitialSize(80);
		paramsPkg.setGrowthMode( new int[]{SomDynamicsIntf._SOM_GROWTH_LATERAL }) ;
		paramsPkg.setGrowthIntensity(5) ;
		
		
		
		paramsPkg.setTopology( 0 ); // 0 = no borders, 1,1+2=3,1+2+3=6, 1+2+3+4 = 10 etc, will be overruled by the template config
		paramsPkg.setSomType( FieldIntf._INSTANCE_TYPE_ASTOR ) ;  
		paramsPkg.setGridType( FieldIntf._SOM_GRIDTYPE_FIXED ) ;
		
		paramsPkg.setSomLevel( TexxDataBaseSettingsIntf._DATABASE_STRUC_RNDDOCS_L1 ) ;
		
		paramsPkg.setHost( somClients, true ) ; // true = use direct connection (OO-invokes) instead of remote connection  
		paramsPkg.hostAddressPort( servicePorts[0]);

		// sometimes we want to re-create the SomData on L2 from L1 nodes
		// or we have to define the nodes source for an update
		paramsPkg.setTransferSourceDbName("astornodes"); // this source
		paramsPkg.setTransferTargetDbName("astordocs");  // this database name will be changed to <name>-<somid> !! 
												 // the metadata will be stored in a separate mapping table
		paramsPkg.setProjectName("astordocs") ;
		
		paramsPkg.setSourceDbName("astordocs");  // this source is for learning the SOM
		paramsPkg.setSourceMode( TexxDataBaseSettingsIntf._DATABASE_SOURCEMODE_CREATE ) ;

		// for the target som: should we prepare fingerprints etc for further abstraction?
		// e.g. for L" som based on histogram this does not make much sense, but for L2 based on Markov that's ok...
		paramsPkg.setAbstractionSupport(1) ; 
		// 0 = no fingerprints AND no database will be created  in case of Astor, e.g. in case of L2 astor
		// 1 = database will be created (for fast query of the large SOM, but NO fingerprints
		// 2 = full abstracton support
        // "0" will prevent creating a fingerprint database

		
		// this reference to s SomId implies an object o the met-level for administration and selection
		// if empty we take the one labeled as "base", if there are several of this, we take the last one
		paramsPkg.setSomId(509897495454545553L); 
		
		// later, in production, the actual project name should differ according to the
		// purpose, i.e. it could include the SomId
		
		paramsPkg.useTemplateConfig( 1 );
		// data stuff etc.paramsPkg
		
		// sfProperties.setCollectibleColumn( index) -> secondary index transported through the nodes...
		
		return paramsPkg;
	}
	
	
	
}














